#! /usr/bin/perl -w

package Scheduler;

use Constants;
use Task;
use Config::ApacheFormat;
use DBI;

our $VERSION = 1.00;

sub new{
	my ($class) = @_;
	my $self = {
		_connection => undef,
		_interval => undef
	};
	bless $self;
	return $self;
}

sub init {
	my ($self) = @_;

	#read configuration from conf file
	my $config = Config::ApacheFormat->new();
	$config->read("scheduler.conf");

	my $block_database = $config->block("server"=>"database");
	my $block_schedule = $config->block("server"=>"schedule");

	#create a connection to the database
	my $host = $block_database->get("HOST");
	my $db = $block_database->get("DB_NAME");
   $self->{_connection} = DBI->connect("DBI:mysql:database=$db;host=$host", 
								$block_database->get("DB_USERNAME"),
								$block_database->get("DB_PASSWORD"), {'RaiseError' => 1});

	$self->{_connection}->{AutoCommit} = 0;
	$self->{_connection}->{RaiseError} = 1;

	#get schedule interval
	$self->{_interval} = $block_schedule->get("INTERVAL");
	$self->{_interval} = 10 unless defined($self->{_interval});
}

sub schedule_tasks { 
	my ($self) = @_;
	
	eval {
		my $taskset_refs = $self->{_connection}->selectall_hashref("SELECT id, stage, tasks FROM
			 tasksets WHERE stage <> $TASKSET_END", "id");

		foreach $taskset_id (sort keys (%$taskset_refs)) {
			my $stage = $taskset_refs->{$taskset_id}->{"stage"};

			#check the taskset which isn't executing yet
			if($stage == $TASKSET_SCHEDULED) {
				#one taskset is combined by several task groups
				#task group should execute in pipeline
				my @task_groups = split(":", $taskset_refs->{$taskset_id}->{"tasks"});

				#one task group is combined by several tasks
				#tasks in a group could execute paralelly
				my @tasks = split(",", $task_groups[0]);

				#flag of whether one task is assigned to available runner
				my $task_running = 0;
				foreach my $task (@tasks) {
					#find the best runner for this task
					my $rid = $self->choose_runner($task);
					
					#schedule the task
					if(defined($rid)){
						$self->{_connection}->do("UPDATE tasks SET rid = $rid, stage = $TASK_SCHEDULED 
							WHERE id = $task AND rid = 0");
						$task_running = 1;
						print "Task $task scheduled to Runner SC $rid\n";
					}
				}
				#set the taskset's status to running after scheduling every task in this taskset
				$self->{_connection}->do("UPDATE tasksets SET stage = $TASKSET_RUNNING 
					WHERE id = $taskset_id") if($task_running != 0);
				
			} elsif($stage == $TASKSET_RUNNING) { #taskset's already running
				my @task_groups = split(":", $taskset_refs->{$taskset_id}->{"tasks"});

				my $task_unfinished = 0;

				foreach my $task_group (@task_groups) {
					my @tasks = split(",", $task_group);
					foreach my $task (@tasks) {
						my $task_ref = $self->{_connection}->selectrow_hashref("SELECT stage, rid FROM tasks
							WHERE id = $task");
						my $stage = $task_ref->{"stage"};

						#if met any unfinished task
						if($stage != $TASK_END) {
							$task_unfinished = 1;

							#start the unrunning task
							if($stage != $TASK_RUNNING && $task_ref->{"rid"} <= 0) {
								#find the best runner for this task
								my $rid = $self->choose_runner($task);

								if(defined($rid)){
									$self->{_connection}->do("UPDATE tasks SET rid = $rid, 
										stage = $TASK_SCHEDULED WHERE id = $task AND stage = $TASK_READY");
									print "Task $task scheduled to Runner $rid\n";
								}
							}
						}
					}

					#the round of check is over if meet a task group which still have running tasks
					last if($task_unfinished);
				}

				if(!$task_unfinished) {
					$self->{_connection}->do("UPDATE tasksets SET stage = $TASKSET_END 
						WHERE id = $taskset_id");
				}
			}
		}
		$self->{_connection}->commit;
	};

   if($@) {
		print "error\n";
   	eval {$self->{_connection}->rollback};
   }

}

sub choose_runner {
	my ($self, $tid) = @_;

	#get the capability of the given task
	my $capability_ref = $self->{_connection}->selectrow_hashref("SELECT capabilities FROM tasks
		WHERE id = $tid");

	my @capability_sets = split /,/, $capability_ref->{"capabilities"};
	my @cap_one = split(":" ,$capability_sets[0]);	
	
	#analysis the capability of a set
	foreach my $capability (@capability_sets) {
		$_ = $capability;
		s/(.*):(.*)/$1 >= $2/;
		$capability = $_;
	}

	my $query_str = join(" AND ", @capability_sets);
	my $rids = $self->{_connection}->selectrow_hashref("SELECT capabilities.rid as rid FROM capabilities, runners 
		WHERE capabilities.rid = runners.id AND $query_str ORDER BY $cap_one[0]");

	return $rids->{"rid"};
}

#main interface of scheduler
sub execute {
	my ($self) = @_;
	while(1) {
		$self->schedule_tasks();
		print "task scheduler sleeps $self->{_interval} seconds...\n";	
		sleep $self->{_interval};
	}
}

